    package com.mars.module.system.service.impl;


    import com.baomidou.mybatisplus.core.metadata.IPage;
    import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;

    import com.baomidou.mybatisplus.core.toolkit.StringUtils;
    import com.baomidou.mybatisplus.core.toolkit.Wrappers;
    import com.mars.common.base.UserContextInfo;
    import com.mars.common.request.sys.*;
    import com.mars.common.util.RequestUtils;
    import com.mars.common.util.TokenUtils;
    import com.mars.common.response.sys.*;
    import com.mars.common.response.sys.LoginResponse;
    import com.mars.common.constant.Constant;
    import com.mars.common.response.PageInfo;
    import com.mars.common.util.ip.IpUtils;
    import com.mars.framework.async.AsyncFactory;
    import com.mars.framework.config.EasyAdminConfig;
    import com.mars.framework.context.ContextUserInfoThreadHolder;
    import com.mars.framework.redis.RedisCache;
    import com.mars.module.admin.entity.SysUserMessageStats;
    import com.mars.module.admin.mapper.SysUserMessageStatsMapper;
    import com.mars.module.system.entity.*;
    import com.mars.framework.exception.ServiceException;
    import com.mars.module.system.mapper.SysRoleMapper;
    import com.mars.module.system.mapper.SysUserMapper;
    import com.mars.module.system.mapper.SysUserPostMapper;
    import com.mars.module.system.mapper.SysUserRoleMapper;
    import com.mars.common.util.IdUtils;
    import com.mars.module.system.service.ISysMenuService;
    import com.mars.module.system.service.ISysUserService;
    import com.mars.module.tool.entity.SysLoginRecord;
    import com.mars.module.tool.mapper.SysLoginRecordMapper;
    import eu.bitwalker.useragentutils.UserAgent;
    import lombok.AllArgsConstructor;
    import org.jetbrains.annotations.NotNull;
    import org.mindrot.jbcrypt.BCrypt;
    import org.springframework.beans.BeanUtils;
    import org.springframework.stereotype.Service;
    import org.springframework.transaction.annotation.Transactional;

    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.time.LocalDateTime;
    import java.util.Date;
    import java.util.List;
    import java.util.Map;
    import java.util.Objects;
    import java.util.concurrent.TimeUnit;
    import java.util.stream.Collectors;

    /**
     * 用户Service
     *
     * @author 源码字节-程序员Mars
     */
    @Service
    @AllArgsConstructor
    @Transactional(rollbackFor = Exception.class)
    public class SysUserServiceImpl implements ISysUserService {

        private final SysUserMapper sysUserMapper;

        private final SysRoleMapper sysRoleMapper;

        private final SysUserRoleMapper sysUserRoleMapper;

        private final ISysMenuService sysMenuService;

        private final SysUserPostMapper sysUserPostMapper;

        private final SysUserMessageStatsMapper sysUserMessageStatsMapper;

        private final RedisCache redisCache;

        private final TokenUtils tokenUtils;

        private final SysLoginRecordMapper sysLoginRecordMapper;

        private final EasyAdminConfig easyAdminConfig;

        @Override
        public SysUser selectByUserName(String userName) {
            return sysUserMapper.selectOne(Wrappers.lambdaQuery(SysUser.class).eq(SysUser::getUserName, userName));
        }

        @Override
        public SysUserDetailResponse get(Long id) {
            //获取用户
            SysUser sysUser = sysUserMapper.selectById(id);
            if (sysUser == null) {
                throw new ServiceException("数据不存在");
            }
            SysUserDetailResponse userVo = new SysUserDetailResponse();
            BeanUtils.copyProperties(sysUser, userVo);

            //获取用户角色
            List<SysRole> roleList = sysRoleMapper.selectByUserId(id);
            if (CollectionUtils.isNotEmpty(roleList)) {
                List<Long> roleIdList = roleList.stream().map(SysRole::getId).collect(Collectors.toList());
                userVo.setRoleId(roleIdList);
            }
            SysUserPost sysPost = sysUserPostMapper.selectOne(Wrappers.lambdaQuery(SysUserPost.class).eq(SysUserPost::getUserId, id));
            if (Objects.nonNull(sysPost)) {
                userVo.setPostId(sysPost.getPostId());
            }
            return userVo;
        }

        @Override
        public PageInfo<SysUserListResponse> pageList(SysUserQueryRequest queryDto) {
            IPage<SysUserListResponse> joinPage = sysUserMapper.selectPageList(queryDto.page(), queryDto);
            List<SysUserListResponse> records = joinPage.getRecords();
            if (CollectionUtils.isNotEmpty(records)) {
                List<Long> userIds = records.stream().map(SysUserListResponse::getId).collect(Collectors.toList());
                List<SysRoleListResponse> sysRoleListResponses = sysRoleMapper.selectUserRoleList(userIds);
                Map<Long, List<SysRoleListResponse>> listMap = sysRoleListResponses.stream().collect(Collectors.groupingBy(SysRoleListResponse::getUserId));
                List<SysUserListResponse> list = records.stream().peek(x -> {
                    x.setRoleList(listMap.get(x.getId()));
                }).collect(Collectors.toList());
                joinPage.setRecords(list);
            }
            return PageInfo.build(joinPage);
        }


        @Override
        @Transactional(rollbackFor = Exception.class)
        public void add(SysUserAddRequest request) {
            if (CollectionUtils.isEmpty(request.getRoleId()) && (!"1".equals(request.getIsRegister()))) {
                throw new ServiceException("请配置角色！");
            }
            if (request.getPostId() == null && (!"1".equals(request.getIsRegister()))) {
                throw new ServiceException("请配置岗位！");
            }
            // 校验登录名称是否重复
            String userName = request.getUserName();
            SysUser user = sysUserMapper.selectOne(Wrappers.lambdaQuery(SysUser.class).eq(SysUser::getUserName, userName));
            if (Objects.nonNull(user)) {
                throw new ServiceException("当前登录名已存在！");
            }
            SysUser sysUser = new SysUser();
            BeanUtils.copyProperties(request, sysUser);
            sysUser.setId(IdUtils.getLongId());
            // 设置默认密码
            sysUser.setPassword(BCrypt.hashpw(Constant.DEFAULT_PASSWORD, BCrypt.gensalt()));
            sysUser.setRealName(request.getRealName());
            sysUser.setCreateTime(LocalDateTime.now());
            sysUser.setUpdateTime(sysUser.getCreateTime());
            sysUser.setAvatar(Constant.DEFAULT_AVATAR);
            sysUserMapper.insert(sysUser);
            if (!"1".equals(request.getIsRegister())) {
                // 新增用户关联角色
                request.getRoleId().forEach(roleId -> {
                    saveUserRole(sysUser, roleId);
                });
                // 添加用户岗位关联关系
                this.saveUserPost(request, sysUser);
            }
        }

        /**
         * 添加用户岗位关联
         *
         * @param request 请求参数
         * @param sysUser sysUser
         */
        private void saveUserPost(SysUserAddRequest request, SysUser sysUser) {
            // 删除关联关系
            sysUserPostMapper.deleteByUserId(sysUser.getId());
            SysUserPost userPost = new SysUserPost();
            userPost.setPostId(request.getPostId());
            userPost.setUserId(sysUser.getId());
            sysUserPostMapper.insert(userPost);
        }

        @Override
        public void delete(Long id) {
            SysUser sysUser = sysUserMapper.selectById(id);
            if (Objects.isNull(sysUser)) {
                throw new ServiceException("数据不存在");
            }
            sysUserMapper.deleteById(id);
        }

        @Override
        @Transactional(rollbackFor = Exception.class)
        public void update(SysUserUpdateRequest request) {
            SysUser sysUser = sysUserMapper.selectById(request.getId());
            if (Objects.isNull(sysUser)) {
                throw new ServiceException("数据不存在");
            }
            BeanUtils.copyProperties(request, sysUser);
            sysUserMapper.updateById(sysUser);
            //删除用户关联角色
            sysUserRoleMapper.deleteByUserId(sysUser.getId());
            if (request.getRoleId() != null) {
                for (Long roleId : request.getRoleId()) {
                    saveUserRole(sysUser, roleId);
                }
            }
            // 删除关联关系 添加用户岗位关联表
            sysUserPostMapper.deleteByUserId(sysUser.getId());
            SysUserPost userPost = new SysUserPost();
            userPost.setPostId(request.getPostId());
            userPost.setUserId(sysUser.getId());
            sysUserPostMapper.insert(userPost);
        }

        private void saveUserRole(SysUser sysUser, Long roleId) {
            SysUserRole sysUserRole = new SysUserRole();
            sysUserRole.setId(IdUtils.getLongId());
            sysUserRole.setUserId(sysUser.getId());
            sysUserRole.setRoleId(roleId);
            sysUserRole.setCreateTime(LocalDateTime.now());
            sysUserRole.setUpdateTime(sysUserRole.getCreateTime());
            sysUserRoleMapper.insert(sysUserRole);
        }

        @Override
        public void dataUpdate(DataUpdateRequest updateDto) {
            // 校验登录名称是否重复
            String userName = updateDto.getUserName();
            SysUser user = sysUserMapper.selectOne(Wrappers.lambdaQuery(SysUser.class)
                    .eq(SysUser::getUserName, userName).ne(SysUser::getId, updateDto.getId()));
            if (Objects.nonNull(user)) {
                throw new ServiceException("当前登录名已存在");
            }
            SysUser sysUser = sysUserMapper.selectById(updateDto.getId());
            if (Objects.isNull(sysUser)) {
                throw new ServiceException("数据不存在");
            }
            BeanUtils.copyProperties(updateDto, sysUser);
            sysUserMapper.updateById(sysUser);
        }


        @Override
        public LoginResponse login(LoginRequest loginRequest, HttpServletRequest request, HttpServletResponse response) {
            String cacheCode = redisCache.getCacheObject(Constant.CAPTCHA_NAME + Constant.COLON_SEPARATOR + loginRequest.getUuid());
            // 用户登录校验
            SysUser sysUser = this.checkLogin(loginRequest, cacheCode);
            LoginResponse loginResponse = new LoginResponse();
            SysUserDetailResponse detailResponse = new SysUserDetailResponse();
            BeanUtils.copyProperties(sysUser, detailResponse);
            loginResponse.setSysUser(detailResponse);
            // 获取菜单列表
            List<SysMenuResponse> menuVoList = sysMenuService.selectByUserId(sysUser.getId());
            loginResponse.setSysMenu(menuVoList);
            //生成令牌
            String token = tokenUtils.createToken(sysUser.getId(), sysUser.getUserName());
            redisCache.setCacheObject(Constant.USER_TOKEN_CACHE + sysUser.getId(), token, easyAdminConfig.getTokenExpireTime(), TimeUnit.MINUTES);
            loginResponse.setToken(token);
            // 异步线程记录登录日志
            HttpServletRequest servletRequest = Objects.requireNonNull(RequestUtils.getRequest());
            AsyncFactory.runAsync(() -> this.saveLoginRecord(servletRequest, loginRequest.getUserName(), sysUser.getId(), "登录成功"));
            return loginResponse;
        }

        /**
         * 保存登录记录
         *
         * @param username username
         * @param message  message
         */
        private void saveLoginRecord(HttpServletRequest request, String username, Long userId, String message) {
            UserAgent userAgent = UserAgent.parseUserAgentString(request.getHeader("User-Agent"));
            String ip = IpUtils.getNetIp();
            String address = IpUtils.getAddress();
            // 获取客户端操作系统
            String os = userAgent.getOperatingSystem().getName();
            // 获取客户端浏览器
            String browser = userAgent.getBrowser().getName();
            SysLoginRecord loginRecord = new SysLoginRecord();
            loginRecord.setUserName(username);
            loginRecord.setIpaddr(ip);
            loginRecord.setLoginLocation(address);
            loginRecord.setBrowser(browser);
            loginRecord.setOs(os);
            loginRecord.setLoginTime(LocalDateTime.now());
            loginRecord.setMsg(message);
            loginRecord.setUserId(userId);
            sysLoginRecordMapper.insert(loginRecord);

        }


        @NotNull
        private SysUser checkLogin(LoginRequest loginRequest, String cacheCode) {
            HttpServletRequest request = Objects.requireNonNull(RequestUtils.getRequest());
            if ("0".equals(loginRequest.getIsMobile())) {
                if (StringUtils.isEmpty(cacheCode)) {
                    AsyncFactory.runAsync(() -> this.saveLoginRecord(request, loginRequest.getUserName(), null, "验证码已过期"));
                    throw new ServiceException("验证码已过期");
                }
                if (!cacheCode.equals(loginRequest.getCode())) {
                    AsyncFactory.runAsync(() -> this.saveLoginRecord(request, loginRequest.getUserName(), null, "验证码有误"));
                    throw new ServiceException("验证码有误");
                }
            }
            SysUser sysUser = this.selectByUserName(loginRequest.getUserName());
            if (Objects.isNull(sysUser)) {
                AsyncFactory.runAsync(() -> this.saveLoginRecord(request, loginRequest.getUserName(), null, "用户名不存在"));
                throw new ServiceException("用户名不存在");
            }
            if (!BCrypt.checkpw(loginRequest.getPassword(), sysUser.getPassword())) {
                AsyncFactory.runAsync(() -> this.saveLoginRecord(request, loginRequest.getUserName(), null, "密码错误"));
                throw new ServiceException("密码错误");
            }
            return sysUser;
        }

        @Override
        public UserInfoResponse getInfo(Long userId, HttpServletRequest request, HttpServletResponse response) {
            UserInfoResponse userInfoResponse = new UserInfoResponse();
            SysUser sysUser = sysUserMapper.selectById(userId);
            SysUserDetailResponse userDetail = new SysUserDetailResponse();
            BeanUtils.copyProperties(sysUser, userDetail);
            userInfoResponse.setSysUser(userDetail);
            List<SysMenuResponse> menuVoList = sysMenuService.selectByUserId(sysUser.getId());
            userInfoResponse.setSysMenu(menuVoList);
            return userInfoResponse;
        }

        @Override
        public void logout(HttpServletRequest request) {
            UserContextInfo userInfo = tokenUtils.getUserInfo(request);
            redisCache.deleteObject(Constant.USER_TOKEN_CACHE + userInfo.getId());
            AsyncFactory.runAsync(() -> this.saveLoginRecord(request, userInfo.getUserName(), userInfo.getId(), "退出登录"));
        }

        @Override
        public Integer getUnReadNum(Long userId) {
            SysUserMessageStats sysUserMessageStats = sysUserMessageStatsMapper.selectOne(Wrappers.lambdaQuery(SysUserMessageStats.class)
                    .eq(SysUserMessageStats::getUserId, userId));
            if (sysUserMessageStats != null) {
                return sysUserMessageStats.getUnRead();
            } else {
                return 0;
            }
        }

        @Override
        @Transactional(rollbackFor = Exception.class)
        public void updatePwd(PwdUpdateRequest updateDto) {
            UserContextInfo userContextInfo = ContextUserInfoThreadHolder.get();
            // 原密码对比
            SysUser sysUser = sysUserMapper.selectById(userContextInfo.getId());
            if (!BCrypt.checkpw(updateDto.getOldPwd(), sysUser.getPassword())) {
                throw new ServiceException("原密码错误");
            }
            if (!updateDto.getNewPwd().equals(updateDto.getConfirmPwd())) {
                throw new ServiceException("两次密码不一致");
            }
            sysUser.setPassword(BCrypt.hashpw(updateDto.getNewPwd(), BCrypt.gensalt()));
            sysUserMapper.updateById(sysUser);
        }

        @Override
        public String resetPwd(Long id) {
            SysUser sysUser = sysUserMapper.selectById(id);
            sysUser.setPassword(BCrypt.hashpw(Constant.DEFAULT_PASSWORD, BCrypt.gensalt()));
            sysUserMapper.updateById(sysUser);
            return Constant.DEFAULT_PASSWORD;
        }


    }
